package it.infodea.tapestrydea.components;

import it.infodea.tapestrydea.support.DeaEventConstants;
import it.infodea.tapestrydea.support.enumerations.CRUDMode;
import it.infodea.tapestrydea.support.interfaces.Identifiable;

import java.util.List;

import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.Block;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.RenderSupport;
import org.apache.tapestry5.annotations.Component;
import org.apache.tapestry5.annotations.Environmental;
import org.apache.tapestry5.annotations.IncludeStylesheet;
import org.apache.tapestry5.annotations.OnEvent;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.Persist;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.beaneditor.BeanModel;
import org.apache.tapestry5.corelib.components.ActionLink;
import org.apache.tapestry5.dom.Element;
import org.apache.tapestry5.internal.TapestryInternalUtils;
import org.apache.tapestry5.services.ClientBehaviorSupport;
import org.apache.tapestry5.ioc.Messages;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.services.BeanModelSource;

@IncludeStylesheet(value = "../assets/styles/tapestrydea.css")
public class CrudDea {

	@Parameter(principal = true, required = true)
	@Property(write = false)
	private Class<? extends Identifiable> elementClass;

	@Parameter
	@Property(write = false)
	private List<Identifiable> elements;

	@Parameter
	@Property
	private Identifiable element;

	/**
	 * The label of the legend in the fieldset form. The field is adding only if
	 * the legendLabel is setted.
	 */
	@SuppressWarnings("unused")
	@Parameter(defaultPrefix = BindingConstants.LITERAL)
	@Property(write = false)
	private String legendLabel;

	/**
	 * The label of the header. The field is adding only if the headerLabel is
	 * setted.
	 */
	@SuppressWarnings("unused")
	@Parameter(defaultPrefix = BindingConstants.LITERAL)
	@Property(write = false)
	private String headerLabel;

	/**
	 * Nome della Ajax Zone. Se omessa sarà generata in automatico di lato alla
	 * lista.
	 */
	@Parameter(defaultPrefix = BindingConstants.LITERAL)
	private String inPlaceZone;

	/**
	 * A comma-separated list of property names to be removed from the BeanModel. The names are case-insensitive. 
	 */
	@SuppressWarnings("unused")
	@Parameter(defaultPrefix = BindingConstants.LITERAL)
	@Property(write = false)
	private String exclude;

	@SuppressWarnings("unused")
	@Parameter
	@Property(write = false)
	private int rowsPerPage = 5;

	@Parameter
	@Property(write = false)
	private boolean allowEdit = true;

	@SuppressWarnings("unused")
	@Parameter
	@Property(write = false)
	private boolean allowCreate = true;

	@Parameter
	@Property(write = false)
	private boolean allowDelete = true;

	/**
	 * A Block to render instead of the table (and pager, etc.) when the source
	 * is empty. The default is simply the text "There is no data to display".
	 * This parameter is used to customize that message, possibly including
	 * components to allow the user to create new objects.
	 */
	@SuppressWarnings("unused")
	@Parameter(value = "block:empty", defaultPrefix = BindingConstants.LITERAL)
	@Property(write = false)
	private Block empty;

	@SuppressWarnings("unused")
	@Component(parameters = { "context=elementZone", "zone=prop:zone" })
	private ActionLink viewLink;

	@SuppressWarnings("unused")
	@Component(parameters = { "context=elementZone", "zone=prop:zone" })
	private ActionLink editLink;

	@SuppressWarnings("unused")
	@Component(parameters = { "context=elementZone", "zone=prop:zone" })
	private ActionLink deleteLink;

	@SuppressWarnings("unused")
	@Component(parameters = { "context=selectedElementZone", "zone=prop:zone" })
	private ActionLink editElement;

	@SuppressWarnings("unused")
	@Component(parameters = { "context=selectedElementZone", "zone=prop:zone" })
	private ActionLink deleteElement;

	@SuppressWarnings("unused")
	@Component(parameters = { "context=prop:zone", "zone=prop:zone" })
	private ActionLink closeAction;

	@SuppressWarnings("unused")
	@Component(parameters = { "zone=prop:zone" })
	private ActionLink createElement;

	@Component(id = "deaform")
	private BeanEditFormDea deaform;

	@Inject
	private Block viewMode;

	@Inject
	private Block editMode;

	@Inject
	private Block gridMode;

	@Inject
	private Block closedMode;

	@Inject
	private Messages messages;

	@Inject
	private ComponentResources resources;

	@Inject
	private BeanModelSource beanModelSource;

	@Environmental
	private RenderSupport renderSupport;

	@Environmental
	private ClientBehaviorSupport clientBehaviorSupport;

	@Persist
	private CRUDMode mode;

	@Persist
	@Property
	private Identifiable selectedElement;

	@Persist
	@Property(write = false)
	private String zone;

	private BeanModel<?> model;
	
	private boolean didRenderZoneDiv;

	/*DEFAULT*/

	public BeanModel<?> getModel() {
		if (model == null) {
			model = beanModelSource.createEditModel(elementClass, resources.getContainerMessages());
		}
		return model;
	}
	

	void beginRender(MarkupWriter writer) {
		if (inPlaceZone != null) {
			zone = inPlaceZone;
		} else {
			zone = renderSupport.allocateClientId(resources);
			clientBehaviorSupport.addZone(zone, "show", "show");
			Element e = writer.element("table");
			e.addClassName("base-crud-zone");
			e = writer.element("tr");
			e = writer.element("td");
			e.addClassName("base-crud-zone");
			e = writer.element("div");
			e.addClassName("base-crud-zone");
			didRenderZoneDiv = true;
		}
	}

	void afterRender(MarkupWriter writer) {
		if (didRenderZoneDiv) {
			writer.end(); // div.base-crud-zone
			writer.end(); // td
			Element e = writer.element("td");
			e.addClassName("internal-crud-zone");
			e = writer.element("div", "id", zone);
			e.addClassName("internal-crud-zone");
			writer.end(); // div.internal-crud-zone
			writer.end(); // td
			writer.end(); // tr
			writer.end(); // table
			didRenderZoneDiv = false;
		}
	}

	@OnEvent(component = "viewLink")
	Object onViewLink(Object id) {
		mode = CRUDMode.READ;
		selectedElement = getElementById(id);
		return switchMode(viewMode);
	}

	@OnEvent(component = "viewLink")
	Object onViewLink(Object id, String zone) {
		handleZone(zone);
		return onViewLink(id);
	}

	@OnEvent(component = "editLink")
	Object onEditLink(Object id) {
		mode = CRUDMode.UPDATE;
		selectedElement = getElementById(id);
		return switchMode(editMode);
	}

	@OnEvent(component = "editLink")
	Object onEditLink(Object id, String zone) {
		handleZone(zone);
		return onEditLink(id);
	}

	@OnEvent(component = "deleteLink")
	Object onDeleteLink(Object id) {
		mode = CRUDMode.DELETE;
		selectedElement = getElementById(id);
		return switchMode(editMode);
	}

	@OnEvent(component = "deleteLink")
	Object onDeleteLink(Object id, String zone) {
		handleZone(zone);
		return onDeleteLink(id);
	}

	@OnEvent(component = "editElement")
	Object onEditElement(Object id, String zone) {
		handleZone(zone);
		return onEditLink(id);
	}

	@OnEvent(component = "deleteElement")
	Object onDeleteElement(Object id, String zone) {
		handleZone(zone);
		return onDeleteLink(id);
	}

	@OnEvent(component = "createElement")
	Object onCreateElement() throws InstantiationException, IllegalAccessException {
		mode = CRUDMode.CREATE;
		selectedElement = elementClass.newInstance();
		return switchMode(editMode);
	}

	@OnEvent(component = "closeAction")
	Object onCloseAction(String zone) throws InstantiationException, IllegalAccessException {
		handleZone(zone);
		return onCancelAction();
	}

	@OnEvent(value = "cancel", component = "deaform")
	void onCancelOperation() throws InstantiationException, IllegalAccessException {
		mode = CRUDMode.LIST;
		selectedElement = elementClass.newInstance();
	}

	@OnEvent(value = "delete", component = "deaform")
	void onDelete(Identifiable element) throws InstantiationException, IllegalAccessException {
		trigger(DeaEventConstants.CONFIRM, selectedElement);
		mode = CRUDMode.LIST;
		selectedElement = elementClass.newInstance();
	}

	@OnEvent(value = "failure", component = "deaform")
	void onFailure(Identifiable element) {
	}

	Object onCancelAction() throws InstantiationException, IllegalAccessException {
		deaform.clearErrors();
		mode = CRUDMode.CANCEL;
		selectedElement = elementClass.newInstance();
		return switchMode(closedMode);
	}

	void onSelectedFromCancel() throws InstantiationException, IllegalAccessException {
		onCancelAction();
	}

	/*PRIVATE*/

	private void trigger(String eventType, Object... params) {
		resources.triggerEvent(eventType, params, null);
	}

	private Object switchMode(Block blockMode) {
		return blockMode;
	}

	private void handleZone(String zone) {
		this.zone = zone;
	}

	private Identifiable getElementById(Object id) {
		if (elements == null) {
			return element;
		}
		for (Identifiable e : elements) {
			Object idValue = e.getIdValue();
			if (idValue instanceof Long && id instanceof String) {
				id = Long.parseLong((String) id);
			}
			if (idValue.equals(id)) {
				return e;
			}
		}
		return null;
	}

	private Object handleError(Identifiable element) throws InstantiationException, IllegalAccessException {
		if (element == null) {
			return onCreateElement();
		}
		return onEditLink(element.getIdValue());
	}

	/*PUBLIC*/

	public Object getInitialMode() throws InstantiationException, IllegalAccessException {
		if (deaform.getHasErrors()) {
			return handleError(element);
		}
		if (elements != null) {
			return gridMode;
		} else if (element != null) {
			return onViewLink(element.getIdValue());
		}
		return onCreateElement();
	}


	public String getSubmitLabel() {
		return (isDeleteMode()) ? resources.getMessages().get("yes-label") : resources.getMessages().get("submit-label");
	}
	
	public String getCancelLabel() {
		return (isDeleteMode()) ? resources.getMessages().get("no-label") : resources.getMessages().get("cancel-label");
	}
	
	public String getConfirmMessage() {
		return resources.getMessages().format("confirm-delete-message", selectedElement.getDisplayId());
	}
	
	public boolean isDeleteMode() {
		return CRUDMode.DELETE == mode;
	}

	/**
	 * Usato come context di viewLink, editLink e deleteLink
	 * 
	 * @return
	 */
	public Object[] getElementZone() {
		return new Object[] { element.getIdValue(), zone };
	}

	/**
	 * Usato come context di editElement e deleteElement
	 * 
	 * @return
	 */
	public Object[] getSelectedElementZone() {
		return new Object[] { selectedElement.getIdValue(), zone };
	}

	public String getDisplayId() {
		Object displayId = element.getDisplayId();
		if (displayId instanceof Enum<?>) {
			return TapestryInternalUtils.getLabelForEnum(messages, (Enum<?>) displayId);
		}
		return (String) displayId;
	}

	public String getToInclude() {
		String str = "view";
		if (allowEdit) {
			str += ",edit";
		}
		if (allowDelete) {
			str += ",delete";
		}
		return str;
	}

	public boolean isEmptyList() {
		return elements == null || elements.size() == 0;
	}

}
